using System;
using NFreeMarker.Ext.Beans;
using NFreeMarker.Template;

namespace NFreeMarker.Core
{
    /// <summary>
    /// An abstract class for nodes in the parse tree that represent a FreeMarker expression.
    /// </summary>
    public abstract class Expression : TemplateObject
    {
        protected abstract ITemplateModel _getAsTemplateModel(Environment env);
        internal abstract bool IsLiteral();

        // Used to store a constant return value for this expression. Only if it
        // is possible, of course.

        protected ITemplateModel ConstantValue;

        //// Hook in here to set the constant value if possible.

        internal override void SetLocation(Template.Template template, int beginColumn, int beginLine, int endColumn, int endLine)
        {
            base.SetLocation(template, beginColumn, beginLine, endColumn, endLine);
            if (!IsLiteral())
            {
                return;
            }

            try
            {
                ConstantValue = _getAsTemplateModel(null);
            }
            catch (Exception)
            {
                // deliberately ignore.
            }
        }

        public ITemplateModel GetAsTemplateModel(Environment env)
        {
            return ConstantValue ?? _getAsTemplateModel(env);
        }

        internal virtual string GetStringValue(Environment env)
        {
            return GetStringValue(GetAsTemplateModel(env), this, env);
        }

        protected static string GetStringValue(ITemplateModel referentModel, Expression exp, Environment env)
        {
            if (referentModel is ITemplateNumberModel)
            {
                return env.FormatNumber(EvaluationUtil.GetNumber((ITemplateNumberModel) referentModel, exp, env));
            }
            if (referentModel is ITemplateDateModel)
            {
                var dm = (ITemplateDateModel) referentModel;
                return env.FormatDate(EvaluationUtil.GetDate(dm, exp, env), dm.GetDateType());
            }
            if (referentModel is ITemplateScalarModel)
            {
                return EvaluationUtil.GetString((ITemplateScalarModel) referentModel, exp, env);
            }
            if (env.IsClassicCompatible())
            {
                if (referentModel is ITemplateBooleanModel)
                {
                    return ((ITemplateBooleanModel) referentModel).GetAsBoolean() ? "true" : "";
                }
                if (referentModel == null)
                {
                    return "";
                }
            }
            AssertNonNull(referentModel, exp, env);

            string msg = "Error " + exp.GetStartLocation()
                         + "\nExpecting a string, "
                         + (env.IsClassicCompatible() ? "boolean, " : "")
                         + "date or number here, Expression " + exp
                         + " is instead a "
                         + referentModel.GetType().FullName;
            throw new NonStringException(msg, env);
        }

        internal virtual Expression DeepClone(string name, Expression subst)
        {
            Expression clone = DeepCloneImpl(name, subst);
            clone.CopyLocationFrom(this);
            return clone;
        }

        protected abstract Expression DeepCloneImpl(string name, Expression subst);

        internal virtual bool IsTrue(Environment env)
        {
            ITemplateModel referent = GetAsTemplateModel(env);
            if (referent is ITemplateBooleanModel)
            {
                return ((ITemplateBooleanModel) referent).GetAsBoolean();
            }
            if (env != null && env.IsClassicCompatible()) // NOTE: Added null check
            {
                return referent != null && !IsEmpty(referent);
            }
            AssertNonNull(referent, this, env);
            string msg = "Error " + GetStartLocation()
                         + "\nExpecting a boolean (true/false) expression here"
                         + "\nExpression " + this + " does not evaluate to true/false "
                         + "\nit is an instance of " + referent.GetType().FullName;
            throw new NonBooleanException(msg, env);
        }

        internal static bool IsEmpty(ITemplateModel model)
        {
            if (model is BeanModel)
            {
                return ((BeanModel) model).IsEmpty();
            }
            if (model is ITemplateSequenceModel)
            {
                return ((ITemplateSequenceModel) model).Size() == 0;
            }
            if (model is ITemplateScalarModel)
            {
                string s = ((ITemplateScalarModel) model).GetAsString();
                return string.IsNullOrEmpty(s);
            }
            if (model is ITemplateCollectionModel)
            {
                return !((ITemplateCollectionModel) model).Iterator().HasNext();
            }
            if (model is ITemplateHashModel)
            {
                return ((ITemplateHashModel) model).IsEmpty();
            }
            if (model is ITemplateNumberModel ||
                model is ITemplateDateModel ||
                model is ITemplateBooleanModel)
            {
                return false;
            }
            return true;
        }
    }
}